package com.vf.cloud.paas.app.agent.common.client.hander;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.jfinal.kit.JsonKit;
import com.jfinal.kit.PropKit;
import com.jfinal.kit.StrKit;
import com.vf.cloud.paas.app.agent.common.server.signalling.player.PlayerServer;
import com.vf.cloud.paas.app.agent.common.server.signalling.pool.StreamerPool;
import com.vf.cloud.paas.app.agent.common.server.signalling.streamer.StreamerServer;
import com.vf.cloud.paas.app.agent.common.server.stun.StunServer;
import com.vf.cloud.paas.app.agent.common.server.turn.TurnServer;
import com.vf.cloud.paas.app.agent.mvc.service.IRender;
import com.vf.cloud.paas.common.cirrus.Cirrus;
import com.vf.cloud.paas.common.util.R;
import com.vf.cloud.paas.common.util.SpringContextHolder;
import com.vf.cloud.paas.common.vo.ROrder;
import io.netty.channel.*;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Handler extends SimpleChannelInboundHandler<Object> {

	private IRender renderImpl;
	private WebSocketClientHandshaker handshaker;
	private ChannelPromise handshakeFuture;

	public Handler(IRender renderImpl) {
		super();
		this.renderImpl = renderImpl;
	}

	@Override
	public void channelActive(ChannelHandlerContext channelHandlerContext) {
		handlerAdded(channelHandlerContext);
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
		ctx.close();
	}

	public void setHandshaker(WebSocketClientHandshaker handshaker) {
		this.handshaker = handshaker;
	}

	public void handlerAdded(ChannelHandlerContext ctx) {
		this.handshakeFuture = ctx.newPromise();
	}

	public ChannelFuture handshakeFuture() {
		return this.handshakeFuture;
	}

	protected void channelRead0(ChannelHandlerContext ctx, Object o) throws Exception {
		// 握手协议返回，设置结束握手
		if (!this.handshaker.isHandshakeComplete()) {
			FullHttpResponse response = (FullHttpResponse) o;
			this.handshaker.finishHandshake(ctx.channel(), response);
			this.handshakeFuture.setSuccess();
			return;
		} else if (o instanceof TextWebSocketFrame) {

			TextWebSocketFrame textWebSocketFrame = (TextWebSocketFrame) o;
			JSONObject rawMsg = null;
			try {
				rawMsg = JSONObject.parseObject(textWebSocketFrame.text());
			} catch (Exception e) {
				log.error(String.format("<- [%s]>> agent cannot parse Pass message:%s >>from master", 1008, e));
				ctx.close();
			}
			if (rawMsg == null) {
				return;
			}

			if (!rawMsg.containsKey("type")) {
				log.error("<- Invalid Pass message:" + textWebSocketFrame.text());
				return;
			}

			String type = rawMsg.getString("type");
			if (StrKit.equals(type, "pong")) {
				return;
			} else if (StrKit.equals(type, "unauthorized")) {
				log.info("<- Received:--"+rawMsg.getString("message"));
				
			} else if (StrKit.equals(type, "NotAvailable")) {
				log.info("<- Received:--"+rawMsg.getString("message"));
				
			} else if (StrKit.equals(type, "connected")) {
				log.info("<- Received:--"+rawMsg.getString("message"));
			} else if (StrKit.equals(type, "onGPUInfo")) {
				log.info("<- Received:--"+rawMsg.getString("message"));
				
			} else if (StrKit.equals(type, "StartClient")) {
				String uuid = rawMsg.getString("uuid");
				R<ROrder> r = renderImpl.AnyOrder(rawMsg);
				Map<String, Object> clientConfig = new HashMap<String, Object>();
				clientConfig.put("type", "onStartClient");
				clientConfig.put("time", System.currentTimeMillis());
				clientConfig.put("r", r);
				clientConfig.put("uuid", uuid);
				send(ctx, JsonKit.toJson(clientConfig));
				
			} else if (StrKit.equals(type, "SynForGPU")) {

				StreamerPool.closeAll();
				log.info("<- Received:--Reset GPU.");
				
				try {
					PropKit.use("application.properties");
					String projectPath = PropKit.get("agent.project.path");

					JSONArray projects = rawMsg.getJSONArray("projects");
					for (int i = 0; i < projects.size(); i++) {
						JSONObject project = (JSONObject) projects.get(i);
						if (project != null) {
							File f = new File(
									String.format("%s\\%s%s", projectPath,project.getString("id"), project.getString("exe_relative_path")));
							if (f.exists()) {
								Process process = Runtime.getRuntime()
										.exec("cmd /c start /b taskkill /f /t /im " + f.getName());
								process.wait();
								process.destroy();
							}
						}
					}
				} catch (Exception e) {
				} finally {
					Cirrus.renderClient.sendGpuInfos(rawMsg.getJSONArray("gpus"));
				}
			} else if (StrKit.equals(type, "SynForSFU")) {

				log.info("<- Received:--Reset SFU.");
				
				JSONObject SFU = rawMsg.getJSONObject("SFU");
				if (SFU != null) {
					String ip = SFU.getString("ip");
					String stunIp = SFU.getString("stunIp");
					String turnIp = SFU.getString("turnIp");
					String username = SFU.getString("username");
					String password = SFU.getString("password");
					int turnPort = SFU.getInteger("turn_port");
					int stunPort = SFU.getInteger("stun_port");
					String enabled = SFU.getString("enabled");

					Cirrus.Local_ip = ip;
					Cirrus.Stun_ip = stunIp;
					Cirrus.Turn_ip = turnIp;
					Cirrus.Turn_username = username;
					Cirrus.Turn_password = password;
					Cirrus.SUF_Enabled = enabled;
					Cirrus.Turn_Port = turnPort;
					Cirrus.Stun_Port = stunPort;

					PropKit.use("application.properties");
					String installPath = PropKit.get("agent.install.path");

					if (StrKit.equals("-1", enabled)) {
						if (Cirrus.Stun_Server != null) {
							Cirrus.Stun_Server.destroy();
							Cirrus.Stun_Server = null;
						}

						if (Cirrus.Turn_Server != null) {
							Cirrus.Turn_Server.destroy();
							Cirrus.Turn_Server = null;
						}
					} else {
						Cirrus.Stun_Server = SpringContextHolder.getBean(StunServer.class);
						Cirrus.Stun_Server.run(installPath, stunPort);

						Cirrus.Turn_Server = SpringContextHolder.getBean(TurnServer.class);
						Cirrus.Turn_Server.run(installPath, turnPort, username, password, ip);
					}
				}
			} else if (StrKit.equals(type, "SynForSignalling")) {
				
				log.info("<- Received:--Reset Signalling.");
				
				JSONObject signalling = rawMsg.getJSONObject("signalling");
				if (signalling != null) {

					Cirrus.Streamer_Ip = signalling.getString("streamer_ip");
					Cirrus.Streamer_Port = signalling.getIntValue("streamer_port");
					Cirrus.Streamer_idleTimeout = signalling.getIntValue("streamer_idle_timeout");
					Cirrus.Player_Ip = signalling.getString("player_ip");
					Cirrus.Player_Port = signalling.getIntValue("player_port");

					if (Cirrus.Streamer_Server != null) {
						Cirrus.Streamer_Server.destroy();
						Cirrus.Streamer_Server = null;
					}
					if (Cirrus.Player_Server != null) {
						Cirrus.Player_Server.destroy();
						Cirrus.Player_Server = null;
					}

					Cirrus.Streamer_Server = SpringContextHolder.getBean(StreamerServer.class);
					Cirrus.Streamer_Server.run();
					Cirrus.Player_Server = SpringContextHolder.getBean(PlayerServer.class);
					Cirrus.Player_Server.run();
				}
			} else if (StrKit.equals(type, "onGPUInit")) {
				log.info("<- Received:--"+rawMsg.getString("message"));
			} else {
				log.error(String.format("<- unsupported Received message type:%s", type));
				ctx.close();
			}
		}
		ctx.flush();
	}

	@Override
	public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
		if (evt instanceof IdleStateEvent) {
			IdleStateEvent event = (IdleStateEvent) evt;
			if (event.state().equals(IdleState.READER_IDLE)) {
				sendPing(ctx, System.currentTimeMillis());
			} else if (event.state().equals(IdleState.WRITER_IDLE)) {
				sendPing(ctx, System.currentTimeMillis());
			} else if (event.state().equals(IdleState.ALL_IDLE)) {
				sendPing(ctx, System.currentTimeMillis());
			}
		}
	}

	public void sendPing(ChannelHandlerContext ctx, long currentTimeMillis) {
		Map<String, Object> clientConfig = new HashMap<String, Object>();
		clientConfig.put("type", "ping");
		clientConfig.put("time", currentTimeMillis);
		sendForPing(ctx, JsonKit.toJson(clientConfig));
	}

	
	public void sendForPing(ChannelHandlerContext ctx, String json) {
		if (ctx != null) {
			ctx.channel().writeAndFlush(new TextWebSocketFrame(json)).addListener(new ChannelFutureListener() {
				@Override
				public void operationComplete(ChannelFuture future) throws Exception {
					if (future.isSuccess()) {
					} else {
					}
				}
			});
		}
	}
	
	public void send(ChannelHandlerContext ctx, String json) {
		if (ctx != null) {
			ctx.channel().writeAndFlush(new TextWebSocketFrame(json)).addListener(new ChannelFutureListener() {
				@Override
				public void operationComplete(ChannelFuture future) throws Exception {
					if (future.isSuccess()) {
						log.info(String.format("-> Agent send a message:%s.",json));
					} else {
						 log.info("-> Sending message failed.");
					}
				}
			});
		}
	}

}